package ba.jamax.ralfdb.persistence.repository.generic;

import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class GenericHibernateRepository<T> extends HibernateDaoSupport implements GenericRepository<T> {

	private Class<T> entityClass;

	@SuppressWarnings("unchecked")
	public GenericHibernateRepository() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
	}
	
	@Autowired
	public void init(SessionFactory factory) {
	    setSessionFactory(factory);
	}

	public T findById(Long id) {
		return getHibernateTemplate().get(entityClass, id);
	}
	
	public Long addNew(T t) {
		return (Long) getHibernateTemplate().save(t);
	}
	
	public void delete(T t){
		getHibernateTemplate().delete(t);
	}
	
	public void update(T t){
		getHibernateTemplate().update(t);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(final Map<String, Object> criterias, final boolean strict,
			final int firstResult, final int maxResults, final Order order) {
		return getHibernateTemplate().executeFind(
				new HibernateCallback<List<T>>() {

					public List<T> doInHibernate(final Session session)
							throws SQLException {
						Criteria criteria = session.createCriteria(entityClass);

						criteria.setFirstResult(firstResult);
						criteria.setMaxResults(maxResults);

						criteria = createCriteria(new HashMap<String, Object>(
								criterias), strict, criteria, order);

						return criteria.list();
					}
				});
	}

    public int countByCriteria(final Map<String, Object> criteriaMap, final boolean strict) {
        return getHibernateTemplate().execute(new HibernateCallback<Integer>() {

            public Integer doInHibernate(final Session session) throws SQLException {
                Criteria criteria = session.createCriteria(entityClass);

                createCriteria(new HashMap<String, Object>(criteriaMap), strict, criteria, null);

                criteria.setProjection(Projections.rowCount());
                return criteria.list().size() > 0 ? ((Number) criteria.list().get(0)).intValue() : 0;
            }
        });
    }

	private Criteria createCriteria(final Map<String, Object> criterias, final boolean strict,
			final Criteria criteria, final Order order) {

		for (String key : criterias.keySet()) {
			Object value = criterias.get(key);
			if (value instanceof String) {
                if (strict) {
                    criteria.add(Restrictions.eq(key, value));
                } else {
                    criteria.add(Restrictions.ilike(key, (String) value, MatchMode.ANYWHERE));
                }
			} else {
				criteria.add(Restrictions.eq(key, value));
			}
		}

        if (order != null) {
        	criteria.addOrder(order);
        }
        
		return criteria;
	}
}
